<?php

/*
    formtypes.formgenerator.php

    This script is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This script is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this script. If not, see <http://www.gnu.org/licenses/>.

    http://www.truefusion.org/
*/

/**
 *	\file formtypes.formgenerator.php
 *	\brief TFCMS Form Types classes
 */

require_once "fieldtypes.formgenerator.php";

abstract class AbstractHtmlForm
	{
		private $action; ///< Stores the URL of the remote or local form handler.
		private $fields = array(); ///< Stores all the InputField objects.
//		private $formFooter;
//		private $formHeader;
		private $formId; ///< Stores the form's HTML ID.
		private $formLayout; ///< Can be used to determine whether to go with tables or otherwise.
		private $formName; ///< Stores the form's HTML name.
		private $method = "post"; ///< Stores the HTTP method desired (and therefore used) for the form.
		private $enctype;

		function __construct($name = "htmlform",
							 $method = "post",
							 $action = NULL)
			{
				$this->setFormName($name);
				$this->setMethod($method);
				$this->setAction($action);
			}

		function getAction()
			{
				return $this->action;
			}

		/*! \breif Set the path you want the data to be submitted to.
		 *
		 *	Normally, you do not need to set this, for the derived
		 *	classes will do the work for you. However, if you want
		 *	another form to handle the data, then set the action.
		 *
		 *	@see getAction()
		 */
		function setAction($url)
			{
				$this->action = (string) $url;
			}

		function getEncType()
			{
				return $this->enctype;
			}

		function setEncType($type)
			{
				$this->enctype = (string) $type;
			}

		function addField(InputField $field)
			{
				$this->fields[] = $field;
			}

		function getFields()
			{
				return $this->fields;
			}

		protected function getFieldNames($getActualNames = false)
		{
			$names = array();

			foreach ($this->getFields() as $field)
			{
				if ($getActualNames)
					$names[] = $field->getActualName();
				else
					$names[] = $field->getName();
			}

			return array_unique($names);
		}

		abstract protected function getFormHeader();

		function getFormId()
			{
				return $this->formId;
			}

		function setFormId($id)
			{
				$this->formId = (string) $id;
			}

		abstract protected function getFormFooter();

		function setFormLayout($layout)
			{
				$this->formLayout = (string) $layout;
			}

		function getFormName()
			{
				return $this->formName;
			}

		function setFormName($name)
			{
				$this->formName = (string) $name;
			}

		/*! \brief Check if form has been submitted.
		 *
		 *	\note Whether or not array_intersect() is slower than
		 *			in_array() is beyond me, but in_array() yielded
		 *			bad (false) results.
		 */
		protected function isFormSubmitted()
			{
				if ($this->getMethod() == "post")
					return (bool) (array_intersect($this->getFieldNames($true), array_keys($_POST)));

				if ($this->getMethod() == "get")
					return (bool) (array_intersect($this->getFieldNames($true), array_keys($_GET)));
			}

		/*! \brief Convenience function called (or should be called) when form passes validation.
		 *
		 *	@see validateFields()
		 */
		abstract protected function formHandler();

		function getMethod()
			{
				return $this->method;
			}

		/*! \brief Set the method for the form.
		 *
		 *	@param string $method Recognized values are: "post" and "get." Only one may be set.
		 *
		 *	@see isFormSubmitted()
		 *	@see getMethod()
		 */
		function setMethod($method)
			{
				if ($method)
					$this->method = $method;
			}

		function renderHtmlForm()
			{
				if ($this->validateFields()
					&& $this->isFormSubmitted())
						$this->formHandler();

				else
				{
					$this->renderFormBegin();
					echo "<table>\n";
					echo "<tr>\n";
					echo "<th colspan=\"2\">";
					$this->getFormHeader();
					echo "</th>";
					echo "</tr>\n";
					foreach ($this->getFields() as $field)
					{
						if (is_subclass_of($field, "ButtonInput"))
							continue;

						echo "<tr>\n";
						echo "<td valign=\"top\">\n";
						$field->renderLabel();
						echo "</td>\n";
						echo "<td valign=\"top\">\n";
						$field->renderField();
						if ($field->hasErrors()
							&& $this->isFormSubmitted())
								$this->listErrors($field);
						echo "</td>\n";
						echo "</tr>\n";
					}
					echo "<tr>\n";
					echo "<td colspan=\"2\" align=\"center\">\n";
					$this->getFormFooter();
					echo "</td>\n";
					echo "</tr>\n";
					echo "</table>\n";
					$this->renderFormEnd();
				}
			}

		/*! \brief Executes each field's verification methods.
		 *
		 *	\return true if all is green; false otherwise.
		 *
		 *	\note To avoid problems with user-friendly values (e.g. "http://"),
		 *		this method should be reimplemented.
		 */
		protected function validateFields()
			{
				$bool = true;

				foreach($this->getFields() as $field)
				{
					if ($field->getValue() == "" //	See note above.
						&& $this->getMethod() == "post")
							$field->setValue($_POST[$field->getActualName()]);

					if ($field->getValue() == ""
						&& $this->getMethod() == "get")
							$field->setValue($_GET[$field->getActualName()]);

					if (!$field->verifyData())
						$bool = false;
				}

				return $bool;
			}

		protected function listErrors(InputField $field)
			{
				echo "<ul class=\"form_errors\">\n";
				foreach ($field->getErrors() as $error)
					echo "<li>$error</li>\n";
				echo "</ul>\n";
			}

		function renderFormBegin()
			{
				echo "<form";

				if ($this->getFormName())
					echo " name=\"".$this->getFormName()."\"";

				if ($this->getFormId())
					echo " id=\"".$this->getFormId()."\"";

				echo " method=\"".$this->getMethod()."\" action=\"".$this->getAction()."\"";

				if ($this->getEncType())
					echo " enctype=\"".$this->getEncType()."\"";

				echo ">\n";
			}

		function renderFormEnd()
			{
				echo "</form>\n";
			}
	}


class ContactForm extends AbstractHtmlForm
	{
		private $bodyField;
		private $contactEmail;
		private $email;
		private $emailField;
		private $emailSubject;
		private $nameField;
		private $resetBtn;
		private $submitBtn;
		private $thankYouText;

		function __construct($name = "contact_form",
							 $method = "post",
							 $action = NULL)
			{
				parent::__construct($name, $method, $action);

				$this->setThankYouText("Thank you for your time; we will get in touch with you shortly.");

				$this->nameField = new TextInput("name");
				$this->nameField->setLabel("Name:");
				$this->nameField->setId("name_field");
				$this->nameField->addApplicableFilters("strip_tags", "htmlspecialchars");
				$this->addField($this->nameField);

				$this->emailField = new TextInput("email");
				$this->emailField->setLabel("E-mail:");
				$this->emailField->setId("email_field");
				$this->emailField->setRegexFilter("/^[\w-]+@[\w-]+\.[\w-]{2,4}$/");
				$this->addField($this->emailField);

				$this->bodyField = new TextArea("body");
				$this->bodyField->setLabel("Message:");
				$this->bodyField->setId("body_field");
				$this->bodyField->addApplicableFilters("strip_tags", "htmlspecialchars");
				$this->addField($this->bodyField);

				//	No need to add these, since they are accessible
				//	 internally and don't require verification.
				$this->resetBtn = new ResetButtonInput;
				$this->submitBtn = new SubmitButtonInput;
			}

		function getContact()
			{
				return $this->contactEmail;
			}

		function setContact($email)
			{
				$this->contactEmail = (string) $email;
			}

		function getEmailSubject()
			{
				return $this->emailSubject;
			}

		function setEmailSubject($subject)
			{
				$this->emailSubject = (string) $subject;
			}

		protected function getFormFooter()
			{
				$this->resetBtn->renderField();
				$this->submitBtn->renderField();
			}

		protected function getFormHeader()
			{
				echo "Contact Us";
			}

		protected function formHandler()
			{
				if (preg_match("/(%0A|%0D|\n|\r)+/i", $this->nameField->getValue()))
					echo "<p>We're sorry, but your form could not be accepted.</p>\n";

				else
				{
					$headers = 'From: '.$this->nameField->getValue().' <'.$this->emailField->getValue().'>' . "\r\n";
					$headers = 'Reply-To: '.$this->nameField->getValue().' <'.$this->emailField->getValue().'>' . "\r\n";
					if (mail($this->getContact(),
							 $this->getEmailSubject(),
							 $this->bodyField->getValue(),
							 $headers))
						echo $this->getThankYouText();

					else
						echo "<p>We are sorry for the inconvenience: there was a problem in sending the e-mail.</p>\n";
				}
			}

		function getThankYouText()
			{
				return $this->thankYouText;
			}

		function setThankYouText($text)
			{
				$this->thankYouText = (string) $text;
			}

		function renderHtmlForm()
			{
				if ($this->validateFields()
					&& $this->isFormSubmitted())
						$this->formHandler();

				else
				{
					$this->renderFormBegin();
					echo "<table>\n";
					echo "<tr>\n";
					echo "<th colspan=\"2\">\n";
					$this->getFormHeader();
					echo "</th>\n";
					echo "</tr>\n";
					echo "<tr>\n";
					echo "<td valign=\"top\">\n";
					$this->nameField->renderLabel();
					echo "</td>\n";
					echo "<td valign=\"top\">\n";
					$this->nameField->renderField();
					if ($this->nameField->hasErrors()
						&& $this->isFormSubmitted())
							$this->listErrors($this->nameField);
					echo "</td>\n";
					echo "</tr>\n";
					echo "<tr>\n";
					echo "<td valign=\"top\">\n";
					$this->emailField->renderLabel();
					echo "</td>\n";
					echo "<td valign=\"top\">\n";
					$this->emailField->renderField();
					if ($this->emailField->hasErrors()
						&& $this->isFormSubmitted())
							$this->listErrors($this->emailField);
					echo "</td>\n";
					echo "</tr>\n";
					echo "<tr>\n";
					echo "<td valign=\"top\">\n";
					$this->bodyField->renderLabel();
					echo "</td>\n";
					echo "<td valign=\"top\">\n";
					$this->bodyField->renderField();
					if ($this->bodyField->hasErrors()
						&& $this->isFormSubmitted())
							$this->listErrors($this->bodyField);
					echo "</td>\n";
					echo "</tr>\n";
					echo "<tr>\n";
					echo "<td colspan=\"2\" align=\"center\">\n";
					$this->getFormFooter();
					echo "</td>\n";
					echo "</tr>\n";
					echo "</table>\n";
					$this->renderFormEnd();
				}
			}
	}

class TextEditorForm extends AbstractHtmlForm
	{
		private $filePath;
		private $overwrite = true;
		private $resetBtn;
		private $submitBtn;
		private $textarea;

		function __construct($name = "text_editor_form",
							 $method = "post",
							 $action = NULL,
							 $filePath = NULL)
			{
				parent::__construct($name, $method, $action);
				$this->setFilePath($filePath);

				$this->textarea = new TextArea("editor");
				$this->textarea->isRequired(false);
				$this->textarea->setColumns(85);
				$this->textarea->setRows(20);
				$this->addField($this->textarea);

				//	No need to add these, since they are accessible
				//	 internally and don't require verification.
				$this->resetBtn = new ResetButtonInput;
				$this->resetBtn->setValue("Revert Changes");

				$this->submitBtn = new SubmitButtonInput;
				$this->submitBtn->setValue("Save Changes");
			}

		protected function getFormFooter()
			{
				$this->resetBtn->renderField();
				$this->submitBtn->renderField();
			}

		protected function formHandler()
			{
				$flags = ($this->getOverwrite()) ? 0 : FILE_APPEND;

				if (@file_put_contents($this->getFilePath(), $this->textarea->getValue(), $flags))
					echo "<p>Changes have been saved.</p>\n";
				else
					echo "<p>There was a problem saving changes: please check file or directory permissions.</p>\n";
			}

		protected function getFormHeader()
			{
				echo "Editing file ".$this->getFilePath();
			}

		function getFilePath()
			{
				return $this->filePath;
			}

		function setFilePath($filePath)
			{
				$this->filePath = (string) $filePath;
			}

		protected function fileGetContents()
			{
				if ($this->getFilePath() != "")
				{
					if (file_exists($this->getFilePath()))
					{
						$this->textarea->setValue(file_get_contents($this->getFilePath()));
						return true;
					}
				}

				return false;
			}

		function getOverwrite()
			{
				return $this->overwrite;
			}

		/*! \brief Whether or not to overwrite the contents of the file.
		 */
		function setOverwrite($yes)
			{
				$this->overwrite = (bool) $yes;
			}

		function renderHtmlForm()
		{
			if ($this->getFilePath() != "")
			{
				if ($this->validateFields()
					&& $this->isFormSubmitted())
						$this->formHandler();

				if (!$this->fileGetContents())
					echo "<div><b>Warning:</b> file does not exist&mdash;will attempt to create file after submission.</div>";

				$this->renderFormBegin();
				echo "<table>\n";
				echo "<tr>\n";
				echo "<th>\n";
				$this->getFormHeader();
				echo "</th>\n";
				echo "</tr>\n";
				echo "<tr>\n";
				echo "<td>\n";
				$this->textarea->renderField();
				echo "</td>\n";
				echo "</tr>\n";
				echo "<tr>\n";
				echo "<td align=\"center\">\n";
				$this->getFormFooter();
				echo "</td>\n";
				echo "</tr>\n";
				echo "</table>\n";
				$this->renderFormEnd();
			}

			else
				echo "<div><b>Error:</b> no file has been specified.</div>";
		}
	}

class FileUploadForm extends AbstractHtmlForm
	{
		private $maxFileSize = 5242880; ///< Default size: 5 megabytes
		private $maxFileSizeField;
		private $uploadDir;
		private $uploadField;
		private $uploadedFile;
		private $submitBtn;

		function __construct($name = "file_upload_form",
							 $method = "post",
							 $action = NULL,
							 $dir = "./uploads")
			{
				parent::__construct($name, $method, $action);
				$this->uploadDir = $dir;

				$this->maxFileSizeField = new HiddenInput("MAX_FILE_SIZE");
				$this->maxFileSizeField->setValue($this->getMaxFileSize());

				$this->uploadField = new FileInput("file_upload[]");
				$this->uploadField->setRequired(false);
				$this->addField($this->uploadField);

				//	No need to add these, since they are accessible
				//	 internally and don't require verification.
				$this->submitBtn = new SubmitButtonInput;
				$this->submitBtn->setValue("Upload");
			}

		function addUploadFields($amount = 1)
			{
				for ($i = 0; $i < $amount; ++$i)
				{
					$field = new FileInput($this->uploadField->getName());
					$field->setRequired(false);
					$this->addField($field);
				}
			}

		protected function getFormFooter()
		{}

		protected function getFormHeader()
		{}

		protected function isFormSubmitted()
			{
				return array_key_exists($this->uploadField->getActualName(), $_FILES);
			}

		protected function formHandler()
			{
				$name = $this->uploadField->getActualName();
				foreach ($_FILES[$name]['error'] as $key => $error)
				{
					switch ($error)
					{
						case UPLOAD_ERR_INI_SIZE:
							$size = ini_get("upload_max_filesize");
							$sizeMeasure = "megabytes";

							if (!ctype_digit($size))
							{
								$measure = strtoupper(substr($size, -1));
								if ($measure == "G")
									$sizeMeasure = "gigabytes";
								else if ($measure == "K")
									$sizeMeasure == "kilobytes";

								$size = (int) $size;
							}

							else
								$size /= 1048576;

							$this->uploadField->storeError("<b>Error:</b> <i>".$_FILES[$name]['name'][$key]."</i>: filesize exceeds maximum server upload size of $size $sizeMeasure.");
							break;

						case UPLOAD_ERR_FORM_SIZE:
							$this->uploadField->storeError("<b>Error:</b> <i>".$_FILES[$name]['name'][$key]."</i>: filesize is bigger than ".round(($this->getMaxFileSize()/1048576), 2)." megabytes.");
							break;

						case UPLOAD_ERR_PARTIAL:
							$this->uploadField->storeError("<b>Error:</b> <i>".$_FILES[$name]['name'][$key]."</i>: file was partially uploaded&mdash;sorry for the inconvenience.");
							break;

						case UPLOAD_ERR_NO_FILE:
							$this->uploadField->storeError("<b>Error:</b> <i>upload field ".($key+1)."</i>: no file was specified.");
							break;

						case UPLOAD_ERR_NO_TMP_DIR:
							$this->uploadField->storeError("<b>Error:</b> <i>".$_FILES[$name]['name'][$key]."</i>: sorry, there was no place to temporarily upload the file to.");
							break;

						case UPLOAD_ERR_CANT_WRITE:
							$this->uploadField->storeError("<b>Error:</b> <i>".$_FILES[$name]['name'][$key]."</i>: sorry, there was a problem writing file to disk.");
							break;

						case UPLOAD_ERR_EXTENSION:
							$this->uploadField->storeError("<b>Error:</b> <i>".$_FILES[$name]['name'][$key]."</i>: file extension not allowed.");
							break;

						default:
							if (move_uploaded_file($_FILES[$name]['tmp_name'][$key],
												   $this->getUploadDir().DIRECTORY_SEPARATOR.$_FILES[$name]['name'][$key]))
									echo "<p><i>".$_FILES[$name]['name'][$key]."</i> successfully uploaded.</p>\n";
							break;
					}
				}
			}

		function getMaxFileSize()
			{
				return $this->maxFileSize;
			}

		function setMaxFileSize($sizeInBytes)
			{
				$this->maxFileSize = (int) $sizeInBytes;
				$this->maxFileSizeField->setValue($this->maxFileSize);
			}

		function setMimeType($type)
			{
				$this->uploadField->setMimeType($type);
			}

		function getUploadDir()
			{
				return $this->uploadDir;
			}

		/*! \brief Set the path of the directory that stores the uploaded files.
		 */
		function setUploadDir($dir)
			{
				// realpath() removes trailing slashes for us.
				// Cast to string in case realpath() returns false.
				$this->uploadDir = (string) realpath($dir);
			}

		function renderHtmlForm()
			{
				// No need to check if directory exists.
				if (is_writable($this->uploadDir))
				{
					if ($this->validateFields()
						&& $this->isFormSubmitted())
							$this->formHandler();

					$this->renderFormBegin();
					$this->maxFileSizeField->renderField();

					if ($this->uploadField->hasErrors()
						&& $this->isFormSubmitted())
							$this->listErrors($this->uploadField);

					$fields = $this->getFields();
					$count = count($fields);
					foreach ($fields as $field)
					{
						$field->renderField();
						if ($count > 1)
							echo "<br/>";
					}

					$this->submitBtn->renderField();
					$this->renderFormEnd();
				}

				else
					echo "<div><b>Error:</b> the upload directory cannot be written to.</div>";
			}
	}

/*	Example usage of ContactForm:

	$form = new ContactForm;
	$form->setAction($_SERVER['PHP_SELF']);
	$form->setContact("admin@localhost");
	$form->renderHtmlForm();
*/
/*	Example usage of TextEditorForm

	$form = new TextEditorForm;
	$form->setFilePath("/path/to/file.txt");
	$form->renderHtmlForm();
*/
/*	Example usage of FileUploadForm:

	$form = new FileUploadForm;
	$form->setEncType("multipart/form-data");
	$form->setUploadDir("/home/reeko5k/www/uploads/");
//	$form->addUploadFields(2);
	$form->renderHtmlForm();
*/

?>
